var Render = require('./render')
var kit = require('./kit')
var hook = require('./hook')
var f = require('./function')
var createRender = Render.createRender
var arrhtml = Render.arrhtml
var CONST = kit.CONST
var isPromise = kit.isPromise
var isArray = kit.isArray
var HOOK = hook.HOOK

/*   My framework */
/*
var Msg = createMsgType()
Msg.is(xxx)
Msg('aa',1,22,3)
*/
function createMsgType(){
  var flag = {'@placeholder@':'msgtype'}
  function r(msg,_1,_2,_3,_4,_5){
    return {
      ctype:CONST.MYMSG,
      type:flag,
      msg:msg,
      val:[].slice.call(arguments).slice(1),
      _target:null,
      _1:_1,
      _2:_2,
      _3:_3,
      _4:_4,
      _5:_5,
      concat:function(a){
        return r.apply(null,[msg].concat(this.val).concat(a))
      }
    }
  }
  r.typeIs = function(a){
    return a&&a.type == flag
  }
  r.is = function(a){
    return function(b){
      return flag == b.type && a == b.msg
    }
  }
  r.eq = function(a,b){
    return flag == b.type && a == b.msg
  }
  return r
}


function isMsg(a){
  return a&&a.ctype == CONST.MYMSG
}
function getMsgVal(msg){
  return msg.val
}

/*
ModelMsg(model,Msg('hello',1,2,3))
var ms = ModelMsg(model,[Msg('msg1'),Msg('msg2'),Msg('msg3')])
data ModelMsg = ModelMsg model [Msg] | Nothing | model
getModel(ms)
getMsg(ms)
*/
function ModelMsg(model,msg){
  return {
    type:CONST.MYMODELMSG,
    msg:msg,
    model:model
  }
}

function modelMsgMap(msg,updatei,viewm){
  var innermsg = f.Nothing
  var rdata = viewm(function(data){
    var ms = updatei(data)
    if(isModelMsg(ms)){
      innermsg = getMsg(ms)
      return getModel(ms)
    }else{
      return ms
    }
  })
  if(innermsg === f.Nothing){
    return rdata
  }else{
    return ModelMsg(rdata,msg.concat(innermsg))
  }
}

function getModel(ms){
  if(isModelMsg(ms)){
    return ms&&ms.model
  }else{
    return ms
  }
}
function getMsg(ms){
  if(isModelMsg(ms)){
    if(isArray(ms.msg)){
      return ms.msg
    }else if(isPromise(ms)){
      return ms
    }else if(ms.msg == null){
      return []
    }else{
      return [ms.msg]
    }
  }else{
    return []
  }
}

function isModelMsg(ms){
  return ms&&ms.type === CONST.MYMODELMSG
}
/*
 alias MS = ModelMsg | Model | Nothing
 init : Model -> MS
 view : Model -> HtmlMsg
 update: (Model,Msg) -> MS
*/
function app(root,options){
  function identity(a){return a}
  var init = options.init || identity
  var update = options.update || identity
  var view = options.view || identity
  var model = options.model
  var render = createRender(root)
  var currentModel = model
  _processModelMsg(init(model))
  if(options.rendered){
    options.rendered(model)
  }else{
    void null
  }

  function _render(modelMsg){
    render(view(getModel(modelMsg)))
  }

  function _processMsg(msgs){
    if(msgs.length == 0){
      void null
    }else{
      var h = msgs[0]
      if(isPromise(h)){
        h.then(function(m){
          processPromise(m).then(function(msg){
            _processModelMsg(update(msg,currentModel))
          })
        })
      }else{
        processPromise(h).then(function(msg){
          _processModelMsg(update(msg,currentModel))
        })
      }
      
      _processMsg(msgs.slice(1))
    }
  }

  // Msg Promise a -> Msg a
  function processPromise(msg){
     return new Promise(function(just){
       var vals = msg.val
       Promise.all(vals).then(function(datas){
         var nvals = datas.map(function(data){
           if(isMsg(data)){
             return processPromise(data)
           }else{
             return data
           }
         })
         Promise.all(nvals).then(function(nnvals){
           msg.val = nnvals
           msg._1 = nnvals[0]
           msg._2 = nnvals[1]
           msg._3 = nnvals[2]
           msg._4 = nnvals[3]
           msg._5 = nnvals[4]
           just(msg)
         })
       })
     }) 
  }
  function _processModelMsg(modelMsg){
    if(modelMsg === f.Nothing){
      void null
    }else{
      currentModel = getModel(modelMsg)
      _render(modelMsg) 
      _processMsg(getMsg(modelMsg))
    }
  }
  eventfn('_click',function(msg,el){
    msg._target = el
    _processMsg([msg])
  })
  eventfn('_input',function(msg,el){
    msg._target = el
    _processMsg([msg])
  })
  HOOK.CREATEDDOM.push(function(dom){
    if(dom._createddom){
      dom._createddom._target = dom
      _processMsg([dom._createddom])
    }
  })
}

/** functions **/
function toModelMsg(ms){
  if(isPromise(ms)){
    return ms.then(function(ms){
      return _fn(ms)
    })
  }else{
    return new Promise(function(just){just(_fn(ms))})
  }
  function _fn(ms){
    if(isModelMsg(ms)){
      return ms
    }else{
      return ModelMsg(ms)
    }
  }
}
function eachPath(fn,e){
  if(e!=null&&isArray(e.path)){
    for(var i=0;i<e.path.length;i++){
      if(e.path[i]._stop){
        fn(e.path[i])
        break
      }else{
        fn(e.path[i])
      }
    }
  }else{
    if(e == null){
      return
    }else{
      var el = e.target || e
      fn(el)
      if(e.path[i]._stop){
        void null
      }else{
        eachPath(fn,el.parentNode)
      }
    }
  }
}
function eventfn(name,fn){
  var eventname = name.slice(1)
  // 点击事件
  document.body.addEventListener(eventname,function(e){
    eachPath(handler,e)
    function handler(el){
      if(isMsg(el[name])){
        fn(el[name],el)
      }else{
        void null
      }
    }
  })
}

module.exports = {
  app:app,
  createMsgType:createMsgType,
  getMsg:getMsg,
  getModel:getModel,
  ModelMsg:ModelMsg,
  modelMsgMap:modelMsgMap,
  isMsg:isMsg,
  getMsgVal:getMsgVal
}